﻿namespace WatiNSahi.Extensions
{
    using System;
    using System.Linq.Expressions;
    using System.Threading;

    using global::WatiN.Core;

    using WatiN.Core.UtilityClasses;

    public static class ElementExtensions
    {
        public static bool IsVisible<TElement>(this TElement element, bool assert = false, bool assertExpectedValue = true) where TElement : Element
        {
            var result = element.Style.Display.ToLower() != "none";

            if (result && ExtensionsSettings.MaxVisibilitySearchScope > 0)
            {
                var parent = element.Parent;

                for (var i = 0; i < ExtensionsSettings.MaxVisibilitySearchScope; i++)
                {
                    if (!result || parent == null)
                    {
                        break;
                    }

                    result = parent.Style.Display.ToLower() != "none";
                    parent = parent.Parent;
                }
            }

            if (assert)
            {

                Assert.AreEqual(assertExpectedValue, result, string.Format("Element of type '{0}' is {1}visible", typeof(TElement).Name, assertExpectedValue ? "not " : string.Empty));

            }

            return result;
        }

        public static bool IsEnabled<TElement>(this TElement element, bool assert = false, bool assertExpectedValue = true) where TElement : Element
        {
            var result = element.Enabled;
            if (assert)
            {

                Assert.AreEqual(assertExpectedValue, result, string.Format("Element of type '{0}' is {1}enabled", typeof(TElement).Name, assertExpectedValue ? "not " : string.Empty));

            }

            return result;
        }

        public static void Action<TElement>(this TElement element, Action<TElement> action, Expression<Func<TElement, bool>> condition) where TElement : Element
        {
            var boolDelegate = condition.Compile();
            var value = boolDelegate(element);

            if (value)
            {
                action(element);
            }
        }

        public static bool WaitUntilExists<TElement>(this TElement element, bool throwExceptionAfterTimeout) where TElement : Element
        {
            return element.WaitUntilEnabled(ExtensionsSettings.WaitUntilExistsTimeout, throwExceptionAfterTimeout);
        }

        public static bool WaitUntilExists<TElement>(this TElement element, int timeoutInMilliseconds = 1000, bool throwExceptionAfterTimeout = true) where TElement : Element
        {
            var timer = new SimpleTimer(new TimeSpan(0, 0, 0, timeoutInMilliseconds));

            do
            {
                if (element.Exists)
                {
                    return true;
                }

                Thread.Sleep(100);
            }
            while (!timer.Elapsed);

            if (throwExceptionAfterTimeout)
            {
                throw new TimeoutException(string.Format("Element of type '{0}' did not exist within the given amount of time ({1} milliseconds)", typeof(Element).Name, timeoutInMilliseconds));
            }

            return false;
        }

        public static bool WaitUntilEnabled<TElement>(this TElement element) where TElement : Element
        {
            return element.WaitUntilEnabled(ExtensionsSettings.WaitUntilEnabledTimeout);
        }

        public static bool WaitUntilEnabled<TElement>(this TElement element, bool throwExceptionAfterTimeout) where TElement : Element
        {
            return element.WaitUntilEnabled(ExtensionsSettings.WaitUntilEnabledTimeout, throwExceptionAfterTimeout);
        }

        public static bool WaitUntilEnabled<TElement>(this TElement element, int timeoutInMilliseconds = 1000, bool throwExceptionAfterTimeout = true) where TElement : Element
        {
            var timer = new SimpleTimer(new TimeSpan(0, 0, 0, timeoutInMilliseconds));

            do
            {
                if (element.Enabled)
                {
                    return true;
                }

                Thread.Sleep(100);
            }
            while (!timer.Elapsed);

            if (throwExceptionAfterTimeout)
            {
                throw new TimeoutException(string.Format("Element of type '{0}' was not enabled within the given amount of time ({1} milliseconds)", typeof(Element).Name, timeoutInMilliseconds));
            }

            return false;
        }

        public static bool Wait<TElement>(this TElement element, int timeoutInMilliseconds) where TElement : Element
        {
            var timer = new SimpleTimer(new TimeSpan(0, 0, 0, timeoutInMilliseconds));

            do
            {
                Thread.Sleep(100);
            }
            while (!timer.Elapsed);

            return true;
        }

        public static bool Wait<TElement>(this TElement element, Expression<Func<TElement, bool>> condition) where TElement : Element
        {
            return element.Wait(condition, ExtensionsSettings.WaitTimeout);
        }

        public static bool Wait<TElement>(this TElement element, Expression<Func<TElement, bool>> condition, bool throwExceptionAfterTimeout) where TElement : Element
        {
            return element.Wait(condition, ExtensionsSettings.WaitTimeout, throwExceptionAfterTimeout);
        }

        public static bool Wait<TElement>(this TElement element, Expression<Func<TElement, bool>> condition, int timeoutInMilliseconds = 1000, bool throwExceptionAfterTimeout = true) where TElement : Element
        {
            var boolDelegate = condition.Compile();

            var timer = new SimpleTimer(new TimeSpan(0, 0, 0, timeoutInMilliseconds));

            do
            {
                var value = boolDelegate(element);

                if (value)
                {
                    return true;
                }

                Thread.Sleep(100);
            }
            while (!timer.Elapsed);

            if (throwExceptionAfterTimeout)
            {
                throw new TimeoutException(string.Format("Element of type '{0}' was not meeting condition specified within the given amount of time ({1} milliseconds)", typeof(Element).Name, timeoutInMilliseconds));
            }

            return false;
        }

        public static bool WaitUntilVisible<TElement>(this TElement element) where TElement : Element
        {
            return element.WaitUntilVisible(ExtensionsSettings.WaitUntilVisibleTimeout);
        }

        public static bool WaitUntilVisible<TElement>(this TElement element, bool throwExceptionAfterTimeout) where TElement : Element
        {
            return element.WaitUntilVisible(ExtensionsSettings.WaitUntilVisibleTimeout, throwExceptionAfterTimeout);
        }

        public static bool WaitUntilVisible<TElement>(this TElement element, int timeoutInMilliseconds = 1000, bool throwExceptionAfterTimeout = true) where TElement : Element
        {
            var timer = new SimpleTimer(new TimeSpan(0, 0, 0, timeoutInMilliseconds));

            do
            {
                if (element.IsVisible())
                {
                    return true;
                }

                Thread.Sleep(100);
            }
            while (!timer.Elapsed);

            if (throwExceptionAfterTimeout)
            {
                throw new TimeoutException(string.Format("Element of type '{0}' was not visible within the given amount of time ({1} milliseconds)", typeof(Element).Name, timeoutInMilliseconds));
            }

            return false;
        }
    }
}
